﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Windows.Controls;
using System.Reflection;
using System.Linq.Expressions;
using System.IO;
using jKinect.Tools;
//using Vector = Microsoft.Kinect.SkeletonPoint;
using Microsoft.Kinect;
using Newtonsoft.Json;
using System.Windows;
using System.Net;

namespace jKinect.Connector
{
    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    [ComVisible(true)]
    public class jKinectInjector : IDisposable
    {
        WebBrowser _webBrowser;

        private Dictionary<string, Delegate> _eventHandlers = new Dictionary<string, Delegate>();

        public event EventHandler<EventArgs> OnQuit;
        private KinectGestureManager _kinectGestureManager = new KinectGestureManager();

        public KinectGestureManager KinectGestureManager
        {
            get { return _kinectGestureManager; }
            set { _kinectGestureManager = value; }
        }

        public jKinectInjector(WebBrowser webBrowser)
        {
            this._webBrowser = webBrowser;
            this._webBrowser.ObjectForScripting = this;
            _kinectGestureManager.KinectManager.KinectReady += new EventHandler(KinectManager_KinectReady);
        }

        public void Dispose()
        {
            //_webBrowser.Dispose();
            KinectGestureManager.KinectManager.Uninitialize();
        }

        public double Width
        {
            get
            {
                return _webBrowser.ActualWidth;
            }
        }

        public double Height
        {
            get
            {
                return _webBrowser.ActualHeight;
            }
        }

        #region JS event handler binding
        //private void AddJSONableEventHandler(string eventName)
        //{
        //    EventHandler<JSONableEventArgs> handler = delegate(object sender, JSONableEventArgs args)
        //    {
        //        InvokeJSHandler(eventName, args);
        //    };
        //    AddEventHandler<JSONableEventArgs>(eventName, handler);
        //}

        //private void AddJointEventHandler(string eventName)
        //{
        //    EventHandler<JointEventArgs> handler = 
        //    AddEventHandler<JointEventArgs>(eventName, handler);
        //}


        private void Add1ParamEventHandler<TEventArgs>(string eventName) where TEventArgs : ParamEventArgs
        {
            AddEventHandler<TEventArgs>(eventName, delegate(object sender, TEventArgs arg)
            {
                InvokeJSHandler(eventName, arg == null ? null : arg.Param);
            });
        }

        private void AddNParamsEventHandler<TEventArgs>(string eventName) where TEventArgs : ParamsEventArgs
        {
            AddEventHandler<TEventArgs>(eventName, delegate(object sender, TEventArgs args)
            {
                InvokeJSHandler(eventName, args.Params.ToArray());
            });
        }

        private void AddJSONEventHandler<TEventArgs>(string eventName) where TEventArgs : JSONableEventArgs
        {
            AddEventHandler<TEventArgs>(eventName, delegate(object sender, TEventArgs arg)
            {
                InvokeJSHandlerWithJSON(eventName, arg);
            });
        }

        private void AddEventHandler<TEventArgs>(string eventName, EventHandler<TEventArgs> handler) where TEventArgs : EventArgs
        {
            _eventHandlers[eventName] = handler;
            EventInfo ei = KinectGestureManager.GetType().GetEvent(eventName + "Handler");
            if (ei != null)
                ei.AddEventHandler(KinectGestureManager, handler);
        }

        #endregion

        #region Browser to Application

        public void InitKinect(dynamic config)
        {
            _eventHandlers.ToList().ForEach(eh => Unbind(eh.Key));
            KinectManagerConfig kinectConfig = new KinectManagerConfig()
            {
                SkeletonFrameReady = KinectGestureManager.KinectManager_SkeletonFrameReady,
                InteractionFrameReady = KinectGestureManager.KinectManager_InteractionFrameReady
            };
            try
            {
                kinectConfig.UseSkeletonStream = config.useSkeletonStream;
            }
            catch
            {
            }

            try
            {
                kinectConfig.UseInteractionStream = config.useInteractionStream;
            }
            catch
            {
            }

            try
            {
                kinectConfig.EventModel = config.eventModel;
            }
            catch
            {
            }


            _kinectGestureManager.KinectManager.InitKinect(kinectConfig);
        }

        public string GetVersion()
        {
            return Assembly.GetEntryAssembly().FullName;
        }

        public void GetNextSkeletonFrame()
        {
            _kinectGestureManager.GetNextSkeletonFrame();
        }

        public void Bind(string eventName)
        {
            switch (eventName)
            {
                case "HandsPosition":
                case "SkeletonFrameReady":
                    AddEventHandler<JointsEventArgs>(eventName, delegate(object sender, JointsEventArgs args)
                    {
                        InvokeJSHandlerWithJSON(eventName, args.Joints);
                    });
                    break;
                case "InteractionFrameReady":
                    AddEventHandler<JointsEventArgs>(eventName, delegate(object sender, JointsEventArgs args)
                    {
                        InvokeJSHandlerWithJSON(eventName, args);
                    });
                    break;
                case "SkeletonFrameReadyCenteredScaled":
                    AddEventHandler<JointsEventArgs>("SkeletonFrameReady", delegate(object sender, JointsEventArgs args)
                    {
                        SkeletonPoint center = args.Joints.ContainsKey("Spine") ? args.Joints["Spine"] : args.Joints["ShoulderCenter"];
                        for (int i = 0; i < args.Joints.Keys.Count; i++)
                        {
                            string jointID = args.Joints.Keys.ElementAt(i);
                            args.Joints[jointID] = args.Joints[jointID].CenteredScaleTo(center, KinectGestureManager.KinectManager, 1, 1);
                        }
                        InvokeJSHandlerWithJSON(eventName, args.Joints);
                    });
                    break;
                case "SkeletonFrameReadyScaled":
                    AddEventHandler<JointsEventArgs>("SkeletonFrameReady", delegate(object sender, JointsEventArgs args)
                    {
                        for (int i = 0; i < args.Joints.Keys.Count; i++)
                        {
                            string jointID = args.Joints.Keys.ElementAt(i);
                            args.Joints[jointID] = args.Joints[jointID].ScaleTo(KinectGestureManager.KinectManager, 1, 1);
                        }
                        InvokeJSHandlerWithJSON(eventName, args.Joints);
                    });
                    break;

                case "HandPositionScaled":
                case "HeadPositionScaled":
                    AddEventHandler<JointEventArgs>(eventName.Replace("Scaled", ""), delegate(object sender, JointEventArgs args)
                    {
                        SkeletonPoint scaled = args.Position.ScaleTo(KinectGestureManager.KinectManager, (float)_webBrowser.ActualWidth, (float)_webBrowser.ActualHeight);
                        InvokeJSHandlerWithJSON(eventName, scaled);
                    });
                    break;

                //PlayerStatus events
                case "PlayerStatus":
                    AddJSONEventHandler<PlayerStatusEventArgs>(eventName);
                    break;

                // 1 ParamEventArgs<string>
                case "HandDirection":
                    Add1ParamEventHandler<ParamEventArgs>(eventName);
                    break;
                // N ParamsEventArgs<string>
                case "HandsDirection":
                case "Swipe":
                    AddNParamsEventHandler<ParamsEventArgs>(eventName);
                    break;
                // JSONableEvents
                case "HandUp":
                case "HandsUp":
                case "HandJoined":
                case "SwipeLeft":
                case "SwipeRight":
                    AddJSONEventHandler<JSONableEventArgs>(eventName);
                    break;
                case "AllPlayersJoints":
                case "AllHeadPositions":
                    AddJSONEventHandler<AllPlayersJointsEventArgs>(eventName);
                    break;
                //Single joint events
                case "HeadPosition":
                case "HandPosition":
                default:
                    AddEventHandler<JointEventArgs>(eventName, delegate(object sender, JointEventArgs args)
                    {
                        InvokeJSHandlerWithJSON(eventName, args.Position);
                    });
                    break;
            }
        }

        public void Unbind(string eventName)
        {
            if (_eventHandlers.ContainsKey(eventName))
            {
                Delegate handler = _eventHandlers[eventName];
                _eventHandlers.Remove(eventName);
                EventInfo ei = KinectGestureManager.GetType().GetEvent(eventName + "Handler");
                if (ei != null)
                    ei.RemoveEventHandler(KinectGestureManager, handler);
            }
        }

        public void SetElevationAngleDelta(int angleDelta)
        {
            try
            {
                _kinectGestureManager.KinectManager.Runtime.ElevationAngle += angleDelta;
            }
            catch (Exception)
            {

            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fovRatio">between -1 to 1</param>
        public void SetElevationAngleBasedOnFOVRatio(float fovRatio)
        {
            try
            {
                _kinectGestureManager.KinectManager.Runtime.ElevationAngle = (int)(
                    fovRatio > 0
                    ? fovRatio * _kinectGestureManager.KinectManager.Runtime.MaxElevationAngle
                    : -fovRatio * _kinectGestureManager.KinectManager.Runtime.MinElevationAngle);
            }
            catch (Exception)
            {

            }
        }
        public void SetElevationAngle(int angle)
        {
            try
            {
                _kinectGestureManager.KinectManager.Runtime.ElevationAngle = angle;
            }
            catch (Exception)
            {

            }
        }

        public int GetElevationAngle()
        {
            return _kinectGestureManager.KinectManager.Runtime.ElevationAngle;
        }

        public string AccelerometerGetCurrentReading()
        {
            return JsonConvert.SerializeObject(1);
            Vector4 cameraAngleAndAcceleration = _kinectGestureManager.KinectManager.Runtime.AccelerometerGetCurrentReading();
            cameraAngleAndAcceleration.W = _kinectGestureManager.KinectManager.Runtime.ElevationAngle;
            return JsonConvert.SerializeObject(cameraAngleAndAcceleration);
        }

        public bool SetSeatedMode(bool active)
        {
            try
            {
                _kinectGestureManager.KinectManager.Runtime.SkeletonStream.TrackingMode =
                    active ? SkeletonTrackingMode.Seated : SkeletonTrackingMode.Default;
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool SetNearMode(bool active)
        {
            try
            {
                _kinectGestureManager.KinectManager.Runtime.DepthStream.Range =
                    active ? DepthRange.Near : DepthRange.Default;
                _kinectGestureManager.KinectManager.Runtime.SkeletonStream.EnableTrackingInNearRange = active;
                return true;
            }
            catch
            {
                return false;
            }
        }

        public void Quit()
        {
            if (OnQuit != null)
                OnQuit(this, null);
        }

        #endregion

        #region Application to Browser

        private void KinectManager_KinectReady(object sender, EventArgs e)
        {
            try
            {
                _webBrowser.InvokeScript("jKinect_Callback", "OnKinectReady");
            }
            catch (Exception ex)
            {
                //  System.Diagnostics.Debugger.Break();
            }
        }

        public void Inject_InlineJScript(string key, string scriptFile)
        {
            if (scriptFile.ToLower().StartsWith("http"))
            {
                WebClient wc = new WebClient();
                try
                {
                    _webBrowser.InvokeScript("execScript", wc.DownloadString(scriptFile));
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Unable to load " + scriptFile + "\r\n" + ex.Message, "Script injection failed");
                }
            }
            else if (File.Exists(scriptFile.Replace("file:///", "")))
                _webBrowser.InvokeScript("execScript", System.IO.File.ReadAllText(scriptFile.Replace("file:///", "")));
            else
                MessageBox.Show("Unable to load " + scriptFile, "Script injection failed");
        }

        public void Inject_JScript(string key, string url)
        {
            string scriptInjector = @"
function inject_" + key + @"() {
    if (typeof(window['" + key + @"']) == 'undefined') {
        var head= document.getElementsByTagName('head')[0];
        var injectedscript = document.createElement('script');
        injectedscript.type = 'text/javascript';
        injectedscript.src = '" + url + @"';
        head.appendChild(injectedscript);
    }
}
setTimeout('inject_" + key + @"()', 400);
";
            try
            {
                _webBrowser.InvokeScript("execScript", scriptInjector);
            }
            catch
            { }
        }

        public void Inject_jKinectScript()
        {
            if (_webBrowser.Source == null) return;
#if DEBUG
            //Inject_JScript("jKinect", "file://" + Environment.CurrentDirectory.Replace("\\", "/") + "/js/jKinect.js");
            Inject_InlineJScript("jKinect", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "js/jKinect.js"));
#else
            Inject_JScript("jKinect", "http://jKinect.com/js/jKinect.js");
#endif
        }

        /// <summary>
        /// Invoke JS Handler with JSON serialization
        /// </summary>
        /// <param name="eventName"></param>
        /// <param name="arg"></param>
        public void InvokeJSHandlerWithJSON(string eventName, object arg)
        {
            try
            {
                _webBrowser.InvokeScript("jKinect_Callback", eventName, true, arg == null ? null : JsonConvert.SerializeObject(arg));
            }
            catch (Exception ex)
            {
                //System.Diagnostics.Debugger.Break();
            }
        }

        public void InvokeJSHandler(string eventName, object arg)
        {
            InvokeJSHandler(eventName, new object[] { arg });
        }

        /// <summary>
        /// Reference to WebBrowser.InvokeMethod.
        /// Used during InvokeJSHandler call to inject arguments array (params)
        /// </summary>
        private static MethodInfo _webBrowserInvokeMethod = null;

        /// <summary>
        /// Invoke JS Handler with any number of parameters (without JSON serialization)
        /// Use Reflection to pass the arguments array (params)
        /// </summary>
        /// <param name="eventName"></param>
        /// <param name="arg"></param>  
        public void InvokeJSHandler(string eventName, object[] args)
        {
            try
            {
                if (_webBrowserInvokeMethod == null)
                    _webBrowserInvokeMethod = typeof(WebBrowser).GetMethod("InvokeScript", new Type[] { typeof(string), typeof(object[]) });
                List<object> argsEx = new List<object>() { eventName, false };
                argsEx.AddRange(args);
                // this line sends an arguments array (params)
                _webBrowserInvokeMethod.Invoke(_webBrowser, new object[] { "jKinect_Callback", argsEx.ToArray() });
                // this line sends a real array as argument
                //_webBrowser.InvokeScript("jKinect_Callback", eventName, false, args);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debugger.Break();
            }
        }

        #endregion
    }
}
